#include <linux/config.h>
#include <linux/version.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/ioport.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/cacheflush.h>

#define CONFIG_TS_DEBUG

#ifdef CONFIG_TS_DEBUG
#define dprintk(msg...)	printk("YunYun: " msg)
#else
#define dprintk(msg...)
#endif

#define TS_MAJOR      120

#define TS_REG_BASE   na_touchcontroller_0
#define TS_IRQ        na_touchcontroller_0_irq

#define CLICK_PERIOD        (HZ/2)  /* 0.5 sec */
#define DOUBLE_CLICK_PERIOD (HZ/2)

#define CON_EVENT          (1 << 24)
#define CON_PRESS_INT_EN   (1 << 25)
#define CON_RELEASE_INT_EN (1 << 26)
#define CLEAR_PRESS_INT    0x01
#define CLEAR_RELEASE_INT  0x02

#define TSIOCTL_MASK_INTR      0x11
#define TSIOCTL_GET_COORDINATE 0x12
#define TSIOCTL_WAIT_EVENT     0x13

/* User Application Commands */
#define TS_INT_BOTH_DISABLE  0x0
#define TS_INT_PRESS_ONLY    0x1
#define TS_INT_RELEASE_ONLY  0x2
#define TS_INT_BOTH_ENABLE   0x3

/* Event Types Flags */
#define FLAG_NONE     0x1
#define FLAG_PRESS    0x2
#define FLAG_RELEASE  0x4
#define FLAG_CLICK    0x8
#define FLAG_DBCLICK  0x10
#define FLAG_DRAG     0x20
int flag = FLAG_NONE;

typedef struct {
	u16 x;
	u16 y;
} COORD;

typedef volatile struct {
	u32 control;
	u32 clrint;
} TS_REG;

/* Global variables */
TS_REG *ts_reg_addr;
COORD pos;
static u8 clicked = 0;
static unsigned long saved_jiffies = 0;
static unsigned long clicked_jiffies = 0;
static unsigned long pressed_jiffies = 0;
static DECLARE_WAIT_QUEUE_HEAD(wq);

/* Touch Screen ISR */
irqreturn_t ts_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
	int type;
	u32 ts_reg;
	
	ts_reg = readl((u32)ts_reg_addr);
	pos.x = ts_reg & 0x00000fff;
	pos.y = (ts_reg & 0x00fff000) >> 12;
	dprintk("X: %d, Y: %d\n", pos.x, pos.y);
	type = ts_reg & CON_EVENT;
	
	if(type) {	/* Press interrupt */
	  flag = FLAG_PRESS;
	  writel(CLEAR_PRESS_INT, (u32)((u8 *)ts_reg_addr + 4));
	  dprintk("Press Interrupted! Press Mask Cleared\n");
	}
	else {      /* Release interrupt */
	  flag = FLAG_RELEASE;
	  writel(CLEAR_RELEASE_INT, (u32)((u8 *)ts_reg_addr + 4));
	  dprintk("Release Interrupted! Release Mask Cleared\n");
	}
	if(waitqueue_active(&wq))
		wake_up_interruptible(&wq);
	else
		dprintk("Process is not waiting for the interrupt!\n");
	return IRQ_HANDLED;
}

int ts_open(struct inode *inode, struct file *filp)
{
	dprintk("Device Opened\n");
	return 0;
}

int ts_release(struct inode *inode, struct file *filp)
{
	dprintk("Device Released\n");
	return 0;
}

int ts_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
{
	u8 mask;
	u32 ts_reg;
	
	switch (cmd) {
		case TSIOCTL_MASK_INTR:
			if(copy_from_user(&mask, (void __user *)arg, sizeof(u8)))
				return -EINVAL;
			switch(mask) {
				case TS_INT_BOTH_DISABLE:
					dprintk("Both press and release interrupt disabled\n");
					ts_reg = 0x0; 
					break;
				case TS_INT_PRESS_ONLY: 
					dprintk("Press interrupt enabled\n");
					ts_reg = CON_PRESS_INT_EN; 
					break;
				case TS_INT_RELEASE_ONLY: 
					dprintk("Release interrupt enabled\n");
					ts_reg = CON_RELEASE_INT_EN; 
					break;
				case TS_INT_BOTH_ENABLE: 
					dprintk("Both press and release interrupt enabled\n");
					ts_reg = CON_PRESS_INT_EN | CON_RELEASE_INT_EN; 
					break;
				default:
					dprintk("Both press and release interrupt enabled\n");
					ts_reg = CON_PRESS_INT_EN | CON_RELEASE_INT_EN; 
					break;
			}
			writel(ts_reg, (u32)ts_reg_addr);
			break;
			
		case TSIOCTL_GET_COORDINATE:
			return copy_to_user((void __user *)arg, &pos, sizeof(COORD)) ? -EFAULT : 0;
		
		case TSIOCTL_WAIT_EVENT:
			/* Wait for interrupt */
			//dprintk("Process is going to sleep...zZzZ!\n");
			flag = FLAG_NONE;
			wait_event_interruptible(wq, flag != FLAG_NONE);
			//dprintk("Process wakes up\n");

			if(flag == FLAG_PRESS) {
			  pressed_jiffies = jiffies;
			}
			else {
				/* To determine the event is release , click or double click */
				saved_jiffies = jiffies;
				if(clicked == 1 && 
					 ((saved_jiffies - pressed_jiffies) <= CLICK_PERIOD) &&
			  	 ((saved_jiffies - clicked_jiffies) <= DOUBLE_CLICK_PERIOD)) {
				 	clicked = 0;
				 	flag = FLAG_DBCLICK | FLAG_RELEASE;
				}
				else if((saved_jiffies - pressed_jiffies) <= CLICK_PERIOD) {
					clicked = 1;
					clicked_jiffies = jiffies;
				 	flag = FLAG_CLICK | FLAG_RELEASE;
				}
				else {
					clicked = 0;
					flag = FLAG_RELEASE;
				}
			}
			return copy_to_user((void __user *)arg, &flag, sizeof(int)) ? -EFAULT : 0;
	}
		
	return 0;
}

struct file_operations ts_fops = {
	ioctl:	ts_ioctl,
	open:		ts_open,
	release:	ts_release,
};

static int __init ts_init(void)
{
	int result;
	
	if (!request_mem_region((unsigned long)TS_REG_BASE, sizeof(TS_REG), "Touch Screen Register")) {
		return -EBUSY;
	}
	dprintk("Got Touch Screen Register\n");
	
	ts_reg_addr = (TS_REG __iomem *)ioremap((unsigned long)TS_REG_BASE, sizeof(TS_REG));
	
	result = register_chrdev(TS_MAJOR, "YunYun Touch Screen", &ts_fops);
	if(result < 0) {
		printk(KERN_WARNING "YunYun: Can't get major %d\n", TS_MAJOR);
		release_mem_region((unsigned long)TS_REG_BASE, sizeof(TS_REG));
		return result;
	}
	
	if((result = request_irq(TS_IRQ, ts_interrupt, SA_INTERRUPT, "YunYun Touch Screen", NULL)) < 0) {
		  printk(KERN_INFO "YunYun: Can't get assigned irq\n");
		  release_mem_region((unsigned long)TS_REG_BASE, sizeof(TS_REG));
		  unregister_chrdev(TS_MAJOR, "YunYun Touch Screen");
		  return result;
	}
	dprintk("Interrupt installed on IRQ %d\n", TS_IRQ);
	  
	return 0;
}

static void __exit ts_cleanup(void) 
{
	unregister_chrdev(TS_MAJOR, "YunYun Touch Screen");
	release_mem_region((unsigned long)TS_REG_BASE, sizeof(TS_REG));
	free_irq(TS_IRQ, NULL);
	printk("YunYun Touch Screen driver removed!\n");
}

module_init(ts_init);
module_exit(ts_cleanup);

MODULE_AUTHOR("Kenny, YunYun");
MODULE_DESCRIPTION("Touch Screen driver for the NIOS2 Dashboard System");
MODULE_LICENSE("GPL");
